/**
 * Copyright (c) 2011-2016, James Zhan 詹波 (jfinal@126.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.mvc;

import com.jfinal.aop.Interceptor;
import com.jfinal.aop.InterceptorManager;
import com.jfinal.config.Routes;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ActionMapping
 */
final class ActionMapping {

    private static final String SLASH = "/";
    private static final InterceptorManager INTERCEPTOR_MANAGER = InterceptorManager.me();
    private final Map<String, Action> mapping = new HashMap<>();

    ActionMapping() {
    }

    private static String buildMsg(String actionKey, Class<? extends Controller> controllerClass, Method method) {

        String msg = "The action \"" + controllerClass.getName() + "." +
                method.getName() + "()\" can not be mapped, " + "actionKey \"" + actionKey +
                "\" is already in use.";
        System.err.println("\nException: " + msg);
        return msg;
    }

    void init(Routes routes) {
        buildActionMapping(routes);
    }

    private Set<String> buildExcludedMethodName() {
        Set<String> excludedMethodName = new HashSet<>();
        Method[] methods = Controller.class.getMethods();
        for (Method m : methods) {
            excludedMethodName.add(m.getName());
        }
        return excludedMethodName;
    }

    private void buildActionMapping(Routes routes) {
        mapping.clear();
        Set<String> excludedMethodName = buildExcludedMethodName();

        for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) {
            Class<? extends Controller> controllerClass = entry.getValue();
            Interceptor[] classInters = INTERCEPTOR_MANAGER.createControllerInterceptor(controllerClass);
            boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
            Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
            for (Method method : methods) {
                String methodName = method.getName();
                if (excludedMethodName.contains(methodName))
                    continue;
                if (sonOfController && !Modifier.isPublic(method.getModifiers()))
                    continue;
                Interceptor[] inters = INTERCEPTOR_MANAGER.buildControllerActionInterceptor(classInters,
                        controllerClass, method);

                String controllerKey = entry.getKey();

                ActionKey ak = method.getAnnotation(ActionKey.class);
                String actionKey;
                if (ak != null) {
                    actionKey = ak.value().trim();
                    if ("".equals(actionKey))
                        throw new IllegalArgumentException(controllerClass.getName() + "." + methodName
                                + "(): The argument of ActionKey can not be blank.");

                    if (!actionKey.startsWith(SLASH))
                        actionKey = SLASH + actionKey;

                    // 判断 actionKey 中是否带有占位符
                    if (actionKey.matches(".+\\{\\w+\\}.*")) {
                        // 将请求路径中的占位符 {\w+} 转换为正则表达式 (\\w+)
                        actionKey = actionKey.replaceAll("\\{\\w+\\}", "(\\\\w+)");
                    }
                    actionKey = controllerKey + actionKey;
                } else if (methodName.equals("index")) {
                    actionKey = controllerKey;
                } else {
                    actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
                }

                Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName,
                        routes.getViewPath(controllerKey), inters);
                if (mapping.put(actionKey, action) != null)
                    throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
            }
        }

        // support url = controllerKey + urlParas with "/" of controllerKey
        Action action = mapping.get("/");
        if (action != null)
            mapping.put("", action);
    }

    /**
     * Support four types of url
     * 1: http://abc.com/controllerKey                 			---> 00
     * 2: http://abc.com/controllerKey/para            			---> 01
     * 3: http://abc.com/controllerKey/method          			---> 10
     * 4: http://abc.com/controllerKey/method/{name}/{age}     	---> 11
     * The controllerKey can also contains "/"
     * Example: http://abc.com/uvw/xyz/method/{name}/{age}
     */
    Action getAction(String url, ArrayList<Object> urlPara) {
        Action action = mapping.get(url);
        if (action != null) {
            return action;
        }

        // --------只能挨个去匹配咯
        for (Map.Entry<String, Action> entry : mapping.entrySet()) {
            String key = entry.getKey();
            Matcher urlMatcher = Pattern.compile(key).matcher(url);
            if (urlMatcher.matches()) {
                // 获取 Handler 及其相关属性
                action = entry.getValue();
                // 获取参数
                for (int i = 0; i < urlMatcher.groupCount(); i++) {
                    urlPara.add(urlMatcher.group(i+1));
                }
                // 若成功匹配，则终止循环
                break;
            }
        }

        return action;
    }

    Map<String, Action> getActionMapping() {
        return mapping;
    }
}
